package cn.yotta.user.core.server.iface.login.impl;

import cn.yotta.common.api.exception.ServiceException;
import cn.yotta.user.core.server.dto.token.TokenDto;
import cn.yotta.user.core.server.dto.user.UserDto;
import cn.yotta.user.core.server.iface.login.LoginUserContext;
import cn.yotta.user.core.server.iface.token.TokenGenerateStrategy;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Joiner;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author wyaoyao
 * @date 2023/3/16 14:56
 */
@Slf4j
public class RedisLoginUserContext implements LoginUserContext {

    private final StringRedisTemplate redisTemplate;

    private final ObjectMapper objectMapper;

    /**
     * 用于保存用户所有的登录token
     */
    private static final String DEFAULT_LOGIN_USER_KEY = "yotta:user:login:id:{0}";

    /**
     * 用于保存token对应的登录用户信息
     */
    private static final String DEFAULT_LOGIN_TOKEN_KEY = "yotta:user:login:token:{0}";

    private final String userMapTokensKey;

    private final String tokenMapToUserKey;
    private static final String EMPTY_STRING = "";

    public RedisLoginUserContext(StringRedisTemplate redisTemplate, ObjectMapper objectMapper, String userMapTokensKey, String tokenMapToUserKey) {
        this.redisTemplate = redisTemplate;
        this.objectMapper = objectMapper;
        this.userMapTokensKey = userMapTokensKey;
        this.tokenMapToUserKey = tokenMapToUserKey;
    }

    public RedisLoginUserContext(StringRedisTemplate redisTemplate, ObjectMapper objectMapper) {
        this(redisTemplate, objectMapper, DEFAULT_LOGIN_USER_KEY, DEFAULT_LOGIN_TOKEN_KEY);
    }

    @Override
    public void addContext(UserDto loginUser, TokenDto token) {
        final String value = encodeUserToJson(loginUser);
        // 保存token 到用户的上下文关系
        redisTemplate.opsForValue().set(getTokenMapToUserKey(token.getToken()), value, token.getExpireSecond(), TimeUnit.SECONDS);
        // 保存用户的token信息，这里就把过期时间设置当前最新token的过期时间，也可以不设置
        final List<String> tokenList = findPrevTokenList(loginUser.getId());
        tokenList.add(token.getToken());
        redisTemplate.opsForValue().set(getUserMapTokensKey(loginUser.getId()),serializeTokenList(tokenList),token.getExpireSecond(), TimeUnit.SECONDS);
    }

    private String serializeTokenList(List<String> tokens) {
        if (CollectionUtils.isEmpty(tokens)) {
            return EMPTY_STRING;
        }
        return Joiner.on(",").join(tokens);

    }
    private List<String> findPrevTokenList(Long userId) {
        List<String> tokenList = new ArrayList<>();

        // 查询之前的 token value
        String originalTokenListStr = redisTemplate.opsForValue().get(getUserMapTokensKey(userId));
        if (StringUtils.isEmpty(originalTokenListStr)) {
            return tokenList;
        }
        String[] tokenArray = originalTokenListStr.split(",");
        Collections.addAll(tokenList, tokenArray);
        return tokenList;
    }

    @Override
    public UserDto getContext(TokenGenerateStrategy.TokenParseResult tokenValue) {
        final String key = getTokenMapToUserKey(tokenValue.getToken());
        final String value = redisTemplate.opsForValue().get(key);
        if (StringUtils.isEmpty(value)) {
            return null;
        }
        return decodeFromJson(value);
    }

    @Override
    public void removeContext(Long id) {
        // 找到这个用户所有的token，删除对应token的用户上下文
        final List<String> tokenList = this.findPrevTokenList(id);
        for (String token : tokenList) {
            this.removeContext(token);
        }
    }

    @Override
    public void removeContext(String token) {
        final String key = this.getTokenMapToUserKey(token);
        redisTemplate.delete(key);
    }

    private UserDto decodeFromJson(String userJson) {
        try {
            return objectMapper.readValue(userJson, UserDto.class);
        } catch (JsonProcessingException e) {
            log.error("serialize json [{}] to login user error by {}", userJson, e.getMessage(), e);
            throw new ServiceException("serialize json to user error");
        }
    }


    private String encodeUserToJson(UserDto loginUser) {
        try {
            return objectMapper.writeValueAsString(loginUser);
        } catch (JsonProcessingException e) {
            log.error("save login user [userID: {}] error by {}", loginUser.getId(), e.getMessage());
            throw new ServiceException("serialize user to json error");
        }
    }

    private String getTokenMapToUserKey(String token) {
        return MessageFormat.format(tokenMapToUserKey, token);
    }

    private String getUserMapTokensKey(Long userId) {
        return MessageFormat.format(userMapTokensKey, userId);
    }

}
